package com.jiangyao.common.utils;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
 * 本地缓存工具
 *
 * @author: yangyongchuan
 * @create: 2020年05月05日 16:22
 */
public class CacheUtil {
	private final static Logger LOGGER = LoggerFactory.getLogger(CacheUtil.class);

	private final static Integer MEMORY_CACHE_SIZE = 2000;
	private static final ConcurrentMap<String, Entry> MEMORY_CACHE = new ConcurrentHashMap<String, Entry>(MEMORY_CACHE_SIZE + 200);

	private static long lastClearTime = System.currentTimeMillis();

	/**
	 * 存入缓存
	 *
	 * @param key    缓存key
	 * @param value  缓存值
	 * @param expire 过期时间，单位秒，0=不过期
	 */
	public static void put(String key, Object value, int expire) {
		if (StringUtils.isBlank(key)) {
			return;
		}
		if (value == null) {
			return;
		}
		try {
			Entry entry = new Entry();
			entry.setObject(SerializeUtil.serialize(value));
			if (expire > 0) {
				entry.setExpire(System.currentTimeMillis() + expire * 1000);
			}
			MEMORY_CACHE.put(key, entry);
			if (MEMORY_CACHE.size() > MEMORY_CACHE_SIZE && lastClearTime - System.currentTimeMillis() > 120000) {
				new clearCacheThread().start();
			}
		} catch (Exception e) {
			LOGGER.error(e.getMessage(), e);
		}
	}

	/**
	 * 获取缓存
	 *
	 * @param key 缓存key
	 * @param <T> 缓存对象class
	 * @return 缓存值
	 */
	@SuppressWarnings("unchecked")
	public static <T> T get(String key) {
		if (StringUtils.isBlank(key)) {
			return null;
		}
		try {
			Entry entry = MEMORY_CACHE.get(key);
			if (entry != null) {
				long expire = entry.getExpire();
				if (expire > 0 && System.currentTimeMillis() > expire) {
					MEMORY_CACHE.remove(key);
				} else {
					entry.setAccess(entry.getAccess() + 1);
					byte[] tmp = entry.getObject();
					return SerializeUtil.unserialize(tmp);
				}
			}
		} catch (Exception e) {
			LOGGER.error(e.getMessage(), e);
			MEMORY_CACHE.remove(key);
		}
		return null;
	}

	/**
	 * 删除缓存
	 *
	 * @param key 缓存key
	 */
	public static void remove(String key) {
		if (StringUtils.isBlank(key)) {
			return;
		}
		MEMORY_CACHE.remove(key);
	}

	private static boolean isClear = false;

	private static class clearCacheThread extends Thread {
		@Override
		public void run() {
			synchronized (MEMORY_CACHE_SIZE) {
				if (isClear) {
					return;
				}
				lastClearTime = System.currentTimeMillis();
				isClear = true;
			}
			try {
				long minAccess = 0;
				for (String key : MEMORY_CACHE.keySet()) {
					Entry entry = MEMORY_CACHE.get(key);
					if (entry != null) {
						long expire = entry.getExpire();
						if (expire > 0 && System.currentTimeMillis() > expire) {
							MEMORY_CACHE.remove(key);
						}
						if (minAccess < 2 || minAccess > entry.getAccess()) {
							minAccess = entry.getAccess();
						}
					}
				}
				if (MEMORY_CACHE.size() > MEMORY_CACHE_SIZE) {
					minAccess = minAccess + 1;
					for (String key : MEMORY_CACHE.keySet()) {
						Entry entry = MEMORY_CACHE.get(key);
						if (entry != null) {
							long access = entry.getAccess();
							if (access < minAccess) {
								MEMORY_CACHE.remove(key);
							}
						}
					}
				}
			} catch (Exception e) {
				LOGGER.error(e.getMessage(), e);
			} finally {
				isClear = false;
			}
		}
	}

	/**
	 * 缓存对象
	 */
	@Data
	@AllArgsConstructor
	@NoArgsConstructor
	private static class Entry {
		private byte[] object;
		private long expire = 0;
		private long access = 1;
	}


	public static void main(String[] args) throws InterruptedException {
		CacheUtil.put("aaaa", "aaaa1111111111a", 0);
		CacheUtil.put("bbbb", "aaaa1111111111a", 8);
		String result = CacheUtil.get("aaaa");
		LOGGER.info("-------:{}", result);
//        for (int i = 0; i < 10; i++) {
//            GuavaCacheUtil.put_str("" + i, i + "_aaaa1111111111a");
//        }
		Thread.sleep(10000);
		result = CacheUtil.get("aaaa");
		LOGGER.info("aaaa-------:{}", result);
		result = CacheUtil.get("bbbb");
		LOGGER.info("bbbb-------:{}", result);
	}

}
